﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Globalization;

namespace SockManager
{
    /// <summary>
    /// 异步TCP客户端
    /// </summary>
    public class AsyncTcpClient : IDisposable
    {
        #region Fields
        private TcpClient tcpClient;
        private bool disposed = false;
        //private int retries = 0; 重连次数

        #endregion

        #region Ctors

        /// <summary>
        /// 异步TCP客户端
        /// </summary>
        /// <param name="remoteEP">远端服务器终结点</param>
        public AsyncTcpClient(IPEndPoint remoteEP)
            : this(new[] { remoteEP.Address }, remoteEP.Port)
        {
        }

        /// <summary>
        /// 异步TCP客户端
        /// </summary>
        /// <param name="remoteEP">远端服务器终结点</param>
        /// <param name="localEP">本地客户端终结点</param>
        public AsyncTcpClient(IPEndPoint remoteEP, IPEndPoint localEP)
            : this(new[] { remoteEP.Address }, remoteEP.Port, localEP)
        {
        }

        /// <summary>
        /// 异步TCP客户端
        /// </summary>
        /// <param name="remoteIPAddress">远端服务器IP地址</param>
        /// <param name="remotePort">远端服务器端口</param>
        public AsyncTcpClient(IPAddress remoteIPAddress, int remotePort)
            : this(new[] { remoteIPAddress }, remotePort)
        {
        }

        /// <summary>
        /// 异步TCP客户端
        /// </summary>
        /// <param name="remoteIPAddress">远端服务器IP地址</param>
        /// <param name="remotePort">远端服务器端口</param>
        /// <param name="localEP">本地客户端终结点</param>
        public AsyncTcpClient(
          IPAddress remoteIPAddress, int remotePort, IPEndPoint localEP)
            : this(new[] { remoteIPAddress }, remotePort, localEP)
        {
        }

        /// <summary>
        /// 异步TCP客户端
        /// </summary>
        /// <param name="remoteHostName">远端服务器主机名</param>
        /// <param name="remotePort">远端服务器端口</param>
        public AsyncTcpClient(string remoteHostName, int remotePort)
            : this(Dns.GetHostAddresses(remoteHostName), remotePort)
        {
        }

        /// <summary>
        /// 异步TCP客户端
        /// </summary>
        /// <param name="remoteHostName">远端服务器主机名</param>
        /// <param name="remotePort">远端服务器端口</param>
        /// <param name="localEP">本地客户端终结点</param>
        public AsyncTcpClient(
          string remoteHostName, int remotePort, IPEndPoint localEP)
            : this(Dns.GetHostAddresses(remoteHostName), remotePort, localEP)
        {
        }

        /// <summary>
        /// 异步TCP客户端
        /// </summary>
        /// <param name="remoteIPAddresses">远端服务器IP地址列表</param>
        /// <param name="remotePort">远端服务器端口</param>
        public AsyncTcpClient(IPAddress[] remoteIPAddresses, int remotePort)
            : this(remoteIPAddresses, remotePort, null)
        {
        }

        /// <summary>
        /// 异步TCP客户端
        /// </summary>
        /// <param name="remoteIPAddresses">远端服务器IP地址列表</param>
        /// <param name="remotePort">远端服务器端口</param>
        /// <param name="localEP">本地客户端终结点</param>
        public AsyncTcpClient(
          IPAddress[] remoteIPAddresses, int remotePort, IPEndPoint localEP)
        {
            this.Addresses = remoteIPAddresses;
            this.Port = remotePort;
            this.LocalIPEndPoint = localEP;
            this.Encoding = Encoding.Default;

            if (this.LocalIPEndPoint != null)
            {
                this.tcpClient = new TcpClient(this.LocalIPEndPoint);
            }
            else
            {
                this.tcpClient = new TcpClient();
            }

            //Retries = 3;
            RetryInterval = 2;
        }

        #endregion

        #region Properties

        /// <summary>
        /// 是否已与服务器建立连接
        /// </summary>
        public bool Connected { get {
                if (tcpClient.Client == null) return false;
                return tcpClient.Client.Connected;
            }
        }
        
        /// <summary>
        /// 远端服务器的IP地址列表
        /// </summary>
        public IPAddress[] Addresses { get; private set; }
        
        /// <summary>
        /// 远端服务器的端口
        /// </summary>
        public int Port { get; private set; }
        
        /// <summary>
        /// 连接重试次数
        /// </summary>
        //public int Retries { get; set; }
        
        /// <summary>
        /// 连接重试间隔
        /// </summary>
        public int RetryInterval { get; set; }
        
        /// <summary>
        /// 远端服务器终结点
        /// </summary>
        public IPEndPoint RemoteIPEndPoint
        {
            get { return new IPEndPoint(Addresses[0], Port); }
        }
        
        /// <summary>
        /// 本地客户端终结点
        /// </summary>
        protected IPEndPoint LocalIPEndPoint { get; private set; }
        
        /// <summary>
        /// 通信所使用的编码
        /// </summary>
        public Encoding Encoding { get; set; }

        #endregion

        #region Connect

        /// <summary>
        /// 连接到服务器
        /// </summary>
        /// <returns>异步TCP客户端</returns>
        public AsyncTcpClient Connect()
        {
            if (!Connected)
            {
                // start the async connect operation
                try
                {
                    tcpClient.BeginConnect(
                  Addresses, Port, HandleTcpServerConnected, tcpClient);
                }catch(Exception e)
                {
                    Console.WriteLine(e.Message);
                }
            }

            return this;
        }

        /// <summary>
        /// 关闭与服务器的连接
        /// </summary>
        /// <returns>异步TCP客户端</returns>
        public AsyncTcpClient Close()
        {
            //if (Connected)
            //{
                //retries = 0;
                tcpClient.Close();

                RaiseServerDisconnected(Addresses, Port);
            //}
            return this;
        }

        #endregion

        #region Receive

        /// <summary>
        /// 处理连接请求后的回调方法
        /// </summary>
        /// <param name="ar"></param>
        private void HandleTcpServerConnected(IAsyncResult ar)
        {
            try
            {
                tcpClient.EndConnect(ar);
                RaiseServerConnected(Addresses, Port);
                //retries = 0;
            }
            catch (Exception ex)
            {
                Console.WriteLine("重新连接("+Addresses[0]+","+Port+")"+ex.Message);
                //ExceptionHandler.Handle(ex);
                //if (retries > 0)
                //{
                //    Logger.Debug(string.Format(CultureInfo.InvariantCulture,
                //      "Connect to server with retry {0} failed.", retries));
                //}

                //retries++;
                //if (retries > Retries)
                //{
                //    // we have failed to connect to all the IP Addresses, 
                //    // connection has failed overall.
                //    RaiseServerExceptionOccurred(Addresses, Port, ex);
                //    return;
                //}
                //else
                //{
                //    //Logger.Debug(string.Format(CultureInfo.InvariantCulture,
                //    //  "Waiting {0} seconds before retrying to connect to server.",
                //    //  RetryInterval));
                //    Thread.Sleep(TimeSpan.FromSeconds(RetryInterval));
                //    Connect();
                //    return;
                //}

                //一直重连
                Thread.Sleep(TimeSpan.FromSeconds(RetryInterval));
                Connect();
                return;
            }

            // 连接成功，开始异步读取数据
            byte[] buffer = new byte[tcpClient.ReceiveBufferSize];
            tcpClient.GetStream().BeginRead(
              buffer, 0, buffer.Length, HandleDatagramReceived, buffer);
        }

        /// <summary>
        /// 读取数据回调方法
        /// </summary>
        /// <param name="ar"></param>
        private void HandleDatagramReceived(IAsyncResult ar)
        {
            try
            {
                NetworkStream stream = tcpClient.GetStream();

                int numberOfReadBytes = 0;
                try
                {
                    numberOfReadBytes = stream.EndRead(ar);
                }
                catch
                {
                    numberOfReadBytes = 0;
                }

                if (numberOfReadBytes == 0)
                {
                    // connection has been closed
                    Close();
                    return;
                }

                // received byte and trigger event notification
                byte[] buffer = (byte[])ar.AsyncState;
                byte[] receivedBytes = new byte[numberOfReadBytes];
                Buffer.BlockCopy(buffer, 0, receivedBytes, 0, numberOfReadBytes);
                RaiseDatagramReceived(tcpClient, receivedBytes);
                //RaisePlaintextReceived(tcpClient, receivedBytes);//处理为ASCALL码 

                // then start reading from the network again
                stream.BeginRead(
                  buffer, 0, buffer.Length, HandleDatagramReceived, buffer);
            }
            catch (Exception e){
                Console.WriteLine(e.Message);
            }

        }

        #endregion

        #region Events

        /// <summary>
        /// 接收到数据报文事件
        /// </summary>
        public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> DatagramReceived;
        /// <summary>
        /// 接收到数据报文明文事件
        /// </summary>
        public event EventHandler<TcpDatagramReceivedEventArgs<string>> PlaintextReceived;


        /// <summary>
        /// 处理接收的数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="datagram"></param>
        private void RaiseDatagramReceived(TcpClient sender, byte[] datagram)
        {
            DatagramReceived?.Invoke(this,new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram));
        }

        /// <summary>
        /// 处理接收的文本数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="datagram"></param>
        private void RaisePlaintextReceived(TcpClient sender, byte[] datagram)
        {
            PlaintextReceived?.Invoke(this,
                new TcpDatagramReceivedEventArgs<string>(sender,
                        this.Encoding.GetString(datagram, 0, datagram.Length))
                );
        }

        /// <summary>
        /// 与服务器的连接已建立事件
        /// </summary>
        public event EventHandler<TcpServerConnectedEventArgs> ServerConnected;
        
        /// <summary>
        /// 与服务器的连接已断开事件
        /// </summary>
        public event EventHandler<TcpServerDisconnectedEventArgs> ServerDisconnected;
        
        /// <summary>
        /// 与服务器的连接发生异常事件
        /// </summary>
        public event EventHandler<TcpServerExceptionOccurredEventArgs> ServerExceptionOccurred;

        /// <summary>
        /// 连接成功触发方法
        /// </summary>
        /// <param name="ipAddresses"></param>
        /// <param name="port"></param>
        private void RaiseServerConnected(IPAddress[] ipAddresses, int port)
        {
            ServerConnected?.Invoke(this, new TcpServerConnectedEventArgs(ipAddresses, port));
        }

        /// <summary>
        /// 连接断开触发方法
        /// </summary>
        /// <param name="ipAddresses"></param>
        /// <param name="port"></param>
        private void RaiseServerDisconnected(IPAddress[] ipAddresses, int port)
        {
            ServerDisconnected?.Invoke(this, new TcpServerDisconnectedEventArgs(ipAddresses, port));
        }

        /// <summary>
        /// 连接失败触发方法
        /// </summary>
        /// <param name="ipAddresses"></param>
        /// <param name="port"></param>
        /// <param name="innerException"></param>
        private void RaiseServerExceptionOccurred(
          IPAddress[] ipAddresses, int port, Exception innerException)
        {
            ServerExceptionOccurred?.Invoke(this,
                new TcpServerExceptionOccurredEventArgs(ipAddresses, port, innerException));
        }

        #endregion

        #region Send

        /// <summary>
        /// 发送报文
        /// </summary>
        /// <param name="datagram">报文</param>
        public void Send(byte[] datagram)
        {
            if (datagram == null)
                throw new ArgumentNullException("datagram");

            if (!Connected)
            {
                RaiseServerDisconnected(Addresses, Port);
                //Connect();
                throw new InvalidProgramException(
                  "This client has not connected to server.");
            }
            try
            {
                 tcpClient.GetStream().BeginWrite(
                  datagram, 0, datagram.Length, HandleDatagramWritten, tcpClient);
            }catch(Exception e)
            {
                Console.WriteLine(e.Message);
            }
            
        }

        /// <summary>
        /// 发送数据回调
        /// </summary>
        /// <param name="ar"></param>
        private void HandleDatagramWritten(IAsyncResult ar)
        {
            ((TcpClient)ar.AsyncState).GetStream().EndWrite(ar);
        }

        /// <summary>
        /// 发送报文
        /// </summary>
        /// <param name="datagram">报文</param>
        public void Send(string datagram)
        {
            Send(this.Encoding.GetBytes(datagram));
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, 
        /// releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed 
        /// and unmanaged resources; <c>false</c> 
        /// to release only unmanaged resources.
        /// </param>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    try
                    {
                        Close();

                    if (tcpClient != null)
                    {
                        tcpClient = null;
                    }
                    }
                    catch (SocketException ex)
                    {
                        //ExceptionHandler.Handle(ex);
                        Console.WriteLine(ex.Source + ex.Message);
                    }
                }

                disposed = true;
            }
        }

        #endregion
    }

    /// <summary>
    /// 与服务器的连接已建立事件参数
    /// </summary>
    public class TcpServerConnectedEventArgs : EventArgs
    {
        /// <summary>
        /// 与服务器的连接已建立事件参数
        /// </summary>
        /// <param name="ipAddresses">服务器IP地址列表</param>
        /// <param name="port">服务器端口</param>
        public TcpServerConnectedEventArgs(IPAddress[] ipAddresses, int port)
        {
            this.Addresses = ipAddresses ?? throw new ArgumentNullException("ipAddresses");
            this.Port = port;
        }

        /// <summary>
        /// 服务器IP地址列表
        /// </summary>
        public IPAddress[] Addresses { get; private set; }
        /// <summary>
        /// 服务器端口
        /// </summary>
        public int Port { get; private set; }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            string s = string.Empty;
            foreach (var item in Addresses)
            {
                s = s + item.ToString() + ',';
            }
            s = s.TrimEnd(',');
            s = s + ":" + Port.ToString(CultureInfo.InvariantCulture);

            return s;
        }
    }

    /// <summary>
    /// 与服务器的连接已断开事件参数
    /// </summary>
    public class TcpServerDisconnectedEventArgs : EventArgs
    {
        /// <summary>
        /// 与服务器的连接已断开事件参数
        /// </summary>
        /// <param name="ipAddresses">服务器IP地址列表</param>
        /// <param name="port">服务器端口</param>
        public TcpServerDisconnectedEventArgs(IPAddress[] ipAddresses, int port)
        {
            this.Addresses = ipAddresses ?? throw new ArgumentNullException("ipAddresses");
            this.Port = port;
        }

        /// <summary>
        /// 服务器IP地址列表
        /// </summary>
        public IPAddress[] Addresses { get; private set; }
        /// <summary>
        /// 服务器端口
        /// </summary>
        public int Port { get; private set; }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            string s = string.Empty;
            foreach (var item in Addresses)
            {
                s = s + item.ToString() + ',';
            }
            s = s.TrimEnd(',');
            s = s + ":" + Port.ToString(CultureInfo.InvariantCulture);

            return s;
        }
    }

    /// <summary>
    /// 与服务器的连接发生异常事件参数
    /// </summary>
    public class TcpServerExceptionOccurredEventArgs : EventArgs
    {
        /// <summary>
        /// 与服务器的连接发生异常事件参数
        /// </summary>
        /// <param name="ipAddresses">服务器IP地址列表</param>
        /// <param name="port">服务器端口</param>
        /// <param name="innerException">内部异常</param>
        public TcpServerExceptionOccurredEventArgs(
          IPAddress[] ipAddresses, int port, Exception innerException)
        {
            this.Addresses = ipAddresses ?? throw new ArgumentNullException("ipAddresses");
            this.Port = port;
            this.Exception = innerException;
        }

        /// <summary>
        /// 服务器IP地址列表
        /// </summary>
        public IPAddress[] Addresses { get; private set; }
        /// <summary>
        /// 服务器端口
        /// </summary>
        public int Port { get; private set; }
        /// <summary>
        /// 内部异常
        /// </summary>
        public Exception Exception { get; private set; }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            string s = string.Empty;
            foreach (var item in Addresses)
            {
                s = s + item.ToString() + ',';
            }
            s = s.TrimEnd(',');
            s = s + ":" + Port.ToString(CultureInfo.InvariantCulture);

            return s;
        }
    }
}
